/**
 * Copyright (C) 2013 Inera AB (http://www.inera.se)
 *
 * This file is part of Inera Certificate Proxy (http://code.google.com/p/inera-certificate-proxy).
 *
 * Inera Certificate Proxy is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Inera Certificate Proxy is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package se.inera.certificate.proxy.filter;

import com.google.common.annotations.VisibleForTesting;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.impl.conn.SchemeRegistryFactory;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.HttpParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import se.inera.certificate.proxy.mappings.Mapper;
import se.inera.certificate.proxy.mappings.Mapping;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Maps.newHashMap;
import static org.apache.commons.lang.StringUtils.defaultIfEmpty;
import static se.inera.certificate.proxy.utils.PathUtil.getPath;

public class ProxyFilter implements Filter, HttpClientProvider, HeaderProvider, InitializingBean {

    private Logger remoteClientPoolLog = LoggerFactory.getLogger("se.inera.certificate.proxy.remoteclientpool");

    private static final int DEFAULT_MAX_CONNECTIONS = 100;

    private static final int DEFAULT_MAX_DEFAULT_PER_ROUTE = 20;

    private static final int DEFAULT_CONNECTION_TIMEOUT_MS = 1000;
    private static final int DEFAULT_SO_TIMEOUT_MS = 3000;
    private static final int DEFAULT_SO_LINGER_S = 3;


    @Autowired
    private Mapper mapper;

    @Autowired(required = false)
    private HeaderProvider headerProvider;

    private HttpClient httpClient = null;

    private PoolingClientConnectionManager connectionManager;

    private int maxConnections = DEFAULT_MAX_CONNECTIONS;

    private int maxDefaultPerRoute = DEFAULT_MAX_DEFAULT_PER_ROUTE;

    private Map<String, String> httpParams = newHashMap();

    private boolean isInitiated = false;

    private String proxyKey = "";

    public ProxyFilter() {
        doInit();
    }

    public void init(FilterConfig filterConfig) throws ServletException {
        doInit();
    }

    private void doInit() {
        if (!isInitiated) {
            connectionManager = new PoolingClientConnectionManager(SchemeRegistryFactory.createDefault());
            connectionManager.setMaxTotal(maxConnections);
            connectionManager.setDefaultMaxPerRoute(maxDefaultPerRoute);

            // TODO: Hardcoded for now... (PW)
            HttpParams params = new BasicHttpParams();
            params.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT_MS);
            params.setParameter(CoreConnectionPNames.SO_TIMEOUT, DEFAULT_SO_TIMEOUT_MS);
            params.setParameter(CoreConnectionPNames.SO_LINGER, DEFAULT_SO_LINGER_S);

            httpClient = new HttpProxyClient(connectionManager, params);

            isInitiated = true;
        }
    }

    @Override
    public void afterPropertiesSet() {
       checkNotNull(mapper, "Mapper is null!");
       checkNotNull(httpClient, "Http Client is null");
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        Mapping m = mapper.getMapping(getPath(httpRequest));

        if (m != null) {
            RequestContextImpl requestContext = new RequestContextImpl(m, this);
            m.getDispatcher(requestContext).dispatch(httpRequest, httpResponse);
            checkConnectionPool();
        } else {
            chain.doFilter(request, response);
        }
    }

    private void checkConnectionPool() {
        // TODO: How do we do this check?? (PW)
        if (connectionManager.getTotalStats().getAvailable() > maxConnections / 2) {
            remoteClientPoolLog.warn("Low number of connections in HttpClient connection pool: {}", connectionManager.getTotalStats().toString());
        }
    }


    public void destroy() {
        // Empty
    }

    public void setMaxConnections(int maxConnections) {
        this.maxConnections = maxConnections;
    }

    public void setMaxDefaultPerRoute(int maxDefaultPerRoute) {
        this.maxDefaultPerRoute = maxDefaultPerRoute;
    }

    public void setHttpParams(Map<String, String> httpParams) {
        this.httpParams = httpParams;
    }

    public void setProxyKey(String proxyKey) {
        this.proxyKey = proxyKey;
    }

    @Override
    public HttpClient getHttpClient() {
        return this.httpClient;
    }

    @VisibleForTesting
    public void setMapper(Mapper mapper) {
        this.mapper = mapper;
    }

    @VisibleForTesting
    public HeaderProvider getHeaderProvider() {
        return headerProvider;
    }

    public void setHeaderProvider(HeaderProvider headerProvider) {
        this.headerProvider = headerProvider;
    }

    @Override
    public Map<String, String> getHeaders(HttpServletRequest httpServletRequest) {
        Map<String, String> headers = newHashMap();
        headers.put("X-Proxy-Original-Servlet-ContextPath", defaultIfEmpty(httpServletRequest.getContextPath(), "/"));
        headers.put("X-Proxy-Original-Url", httpServletRequest.getRequestURL().toString());
        headers.put("X-Proxy-Key", this.proxyKey);
        if (headerProvider != null) {
            headers.putAll(headerProvider.getHeaders(httpServletRequest));
        }
        return headers;
    }

}
